#include "SehnsuchtPrecompiled.hxx"

#ifdef REIGN_DESKTOP // Desktop implementation

#include "SehnsuchtDirectXBaseDesktop.hxx"

#define _Get_window_device_context_for_client_region GetDC
#define _Get_window_device_context_for_entire_region GetWindowDC
#define _Get_window_device_context _Get_window_device_context_for_client_region

#define _Get_width_from_rect(rect) (rect.right - rect.left)
#define _Get_height_from_rect(rect) (rect.bottom - rect.top)
#define _Convert_from_win32rect_to_d2d1rect(winRect, d2dRect) \
	d2dRect.bottom = _To_float winRect.bottom; \
	d2dRect.top = _To_float winRect.top; \
	d2dRect.right = _To_float winRect.right; \
	d2dRect.left = _To_float winRect.left;

Sehnsucht::DirectXBaseDesktop::~DirectXBaseDesktop()
{
	PrintToDebugWindowOnly8("Sehnsucht. Deleting directx base for desktop.");
}

Sehnsucht::DirectXBaseDesktop::DirectXBaseDesktop()
{
	PrintToDebugWindowOnly8("Sehnsucht. Creating directx base for desktop.");

	m_window = GetForegroundWindow(); if (m_window)
	{
		auto screenHandle = _Get_window_device_context(m_window);
		m_dpi = _To_single GetDeviceCaps(screenHandle, LOGPIXELSX);
		AutoDebugVariable(YDpi) (Float32) (GetDeviceCaps(screenHandle, LOGPIXELSY));
		ReleaseDC(m_window, screenHandle);
		screenHandle = nullptr;
	}
}

void Sehnsucht::DirectXBaseDesktop::Initialize()
{
	PrintToDebugWindowOnly8("Sehnsucht. Initializing directx base for desktop.");

	CreateDeviceIndependentResources();
	CreateDeviceResources();
	CreateWindowSizeDependentResources();
}

void Sehnsucht::DirectXBaseDesktop::ValidateDevice()
{
	PrintToDebugWindowOnly8("Sehnsucht. Validating the device for directx base for desktop.");

	using namespace Microsoft::WRL;
	using namespace Sehnsucht;
	using namespace Microsoft;

	ComPtr<IDXGIDevice1> dxgiDevice;
	ComPtr<IDXGIAdapter> deviceAdapter;
	DXGI_ADAPTER_DESC deviceDesc;
	ThrowIfFailed(m_d3dDevice.As(&dxgiDevice));
	ThrowIfFailed(dxgiDevice->GetAdapter(&deviceAdapter));
	ThrowIfFailed(deviceAdapter->GetDesc(&deviceDesc));

	ComPtr<IDXGIFactory2> dxgiFactory;
	ComPtr<IDXGIAdapter1> currentAdapter;
	DXGI_ADAPTER_DESC currentDesc;
	ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory)));
	ThrowIfFailed(dxgiFactory->EnumAdapters1(0, &currentAdapter));
	ThrowIfFailed(currentAdapter->GetDesc(&currentDesc));

	if (deviceDesc.AdapterLuid.LowPart != currentDesc.AdapterLuid.LowPart ||
		deviceDesc.AdapterLuid.HighPart != currentDesc.AdapterLuid.HighPart ||
		FAILED(m_d3dDevice->GetDeviceRemovedReason()))
	{
		dxgiDevice = nullptr;
		deviceAdapter = nullptr;

		HandleDeviceLost();
	}
}

// These are the resources required independent of the device.
void Sehnsucht::DirectXBaseDesktop::CreateDeviceIndependentResources()
{
	PrintToDebugWindowOnly8("Sehnsucht. Creating the device independent resources for directx base for desktop.");

	using namespace Microsoft::WRL;
	using namespace Sehnsucht;
	using namespace Microsoft;

	D2D1_FACTORY_OPTIONS options;
	ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));

#if defined(_DEBUG)
	// If the project is in a debug build, enable Direct2D debugging via SDK Layers.
	options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif

	ThrowIfFailed(
		D2D1CreateFactory(
			D2D1_FACTORY_TYPE_MULTI_THREADED,
			//D2D1_FACTORY_TYPE_SINGLE_THREADED,
			__uuidof(ID2D1Factory1),
			&options,
			&m_d2dFactory
			)
		);

	ThrowIfFailed(
		DWriteCreateFactory(
			DWRITE_FACTORY_TYPE_SHARED,
			__uuidof(IDWriteFactory),
			&m_dwriteFactory
			)
		);

	ThrowIfFailed(
		CoCreateInstance(
			CLSID_WICImagingFactory,
			nullptr,
			CLSCTX_INPROC_SERVER,
			IID_PPV_ARGS(&m_wicFactory)
			)
		);
}

// These are the resources that depend on the device.
void Sehnsucht::DirectXBaseDesktop::CreateDeviceResources()
{
	PrintToDebugWindowOnly8("Sehnsucht. Creating the device resources for directx base for desktop.");

	using namespace Microsoft::WRL;
	using namespace Sehnsucht;
	using namespace Microsoft;

	// This flag adds support for surfaces with a different color channel ordering
	// than the API default. It is required for compatibility with Direct2D.
	UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
	ComPtr<IDXGIDevice> dxgiDevice;

#if defined(_DEBUG)
	// If the project is in a debug build, enable debugging via SDK Layers with this flag.
	creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

	// This array defines the set of DirectX hardware feature levels this app will support.
	// Note the ordering should be preserved.
	// Don't forget to declare your application's minimum required feature level in its
	// description.  All applications are assumed to support 9.1 unless otherwise stated.
	D3D_FEATURE_LEVEL featureLevels [] =
	{
		D3D_FEATURE_LEVEL_11_1,
		D3D_FEATURE_LEVEL_11_0,
		D3D_FEATURE_LEVEL_10_1,
		D3D_FEATURE_LEVEL_10_0,
		D3D_FEATURE_LEVEL_9_3,
		D3D_FEATURE_LEVEL_9_2,
		D3D_FEATURE_LEVEL_9_1
	};

	// Create the Direct3D 11 API device object and a corresponding context.
	ComPtr<ID3D11Device> d3dDevice;
	ComPtr<ID3D11DeviceContext> d3dContext;
	ThrowIfFailed(
		D3D11CreateDevice(
			nullptr, // Specify nullptr to use the default adapter.
			m_driver, //D3D_DRIVER_TYPE_HARDWARE,
			0, // Software driver handle
			creationFlags, // Set debug and Direct2D compatibility flags.
			featureLevels, // List of feature levels this app can support.
			ARRAYSIZE(featureLevels),
			D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Windows Store apps.
			&d3dDevice, // Returns the Direct3D device created.
			&m_d3dFeatureLevel, // Returns feature level of device created.
			&d3dContext // Returns the device immediate context.
			)
		);

	// Get the Direct3D 11.1 API device and context interfaces.
	ThrowIfFailed(
		d3dDevice.As(&m_d3dDevice)
		);

	ThrowIfFailed(
		d3dContext.As(&m_d3dContext)
		);

	// Get the underlying DXGI device of the Direct3D device.
	ThrowIfFailed(
		m_d3dDevice.As(&dxgiDevice)
		);

	// Create the Direct2D device object and a corresponding context.
	ThrowIfFailed(
		m_d2dFactory->CreateDevice(
			dxgiDevice.Get(), 
			&m_d2dDevice
			)
		);

	ThrowIfFailed(
		m_d2dDevice->CreateDeviceContext(
			D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS,
			&m_d2dContext
			)
		);
}

void Sehnsucht::DirectXBaseDesktop::UpdateForWindowSizeChange()
{
	PrintToDebugWindowOnly8("Sehnsucht. Updating directx base for desktop on window size changed.");

	static ID3D11RenderTargetView* s_nullViews [] = { nullptr };

	{
		DEVMODE deviceMode; ZeroValue(&deviceMode);
		deviceMode.dmSize = sizeof DEVMODE;
		auto statusOnEnumSettings = EnumDisplaySettingsEx(
			nullptr, ENUM_CURRENT_SETTINGS, 
			&deviceMode, EDS_ROTATEDMODE
			);

		RECT clientBounds; ZeroValue(&clientBounds);
		auto statusOnGetRect = GetClientRect(
			m_window, &clientBounds
			);

		// Check wheather the updated is really needed.
		if ((_Get_width_from_rect(clientBounds) == _Get_width_from_rect(m_windowBounds)) &&
			(_Get_height_from_rect(clientBounds) == _Get_height_from_rect(m_windowBounds)) &&
			(m_orientation == deviceMode.dmOrientation)) return;
	}

	m_d3dContext->OMSetRenderTargets(ARRAYSIZE(s_nullViews), s_nullViews, nullptr);
	m_d2dContext->SetTarget(nullptr);
	m_d3dRenderTargetView = nullptr;
	m_d3dDepthStencilView = nullptr;
	m_d2dTargetBitmap = nullptr;

	m_d3dContext->Flush();

	CreateWindowSizeDependentResources();
}

// Allocate all memory resources that change on a window SizeChanged event.
void Sehnsucht::DirectXBaseDesktop::CreateWindowSizeDependentResources()
{
	PrintToDebugWindowOnly8("Sehnsucht. Creating the window size dependent resources for directx base for desktop.");

	using namespace Microsoft::WRL;
	using namespace Sehnsucht;
	using namespace Microsoft;
	using namespace DirectX;
	using namespace D2D1;

	RECT clientBounds; ZeroValue(&clientBounds);
	auto statusOnGetRect = GetClientRect(
		m_window, &clientBounds
		);
	DEVMODE deviceMode; ZeroValue(&deviceMode);
	deviceMode.dmSize = sizeof DEVMODE;
	auto statusOnEnumSettings = EnumDisplaySettingsEx(
		nullptr, ENUM_CURRENT_SETTINGS,
		&deviceMode, EDS_ROTATEDMODE
		);

	// Store the window bounds so the next time we get a SizeChanged event we can
	// avoid rebuilding everything if the size is identical.
	_Convert_from_win32rect_to_d2d1rect(clientBounds, m_windowBounds);
	//m_windowBounds = m_window->Bounds;

	// Calculate the necessary swap chain and render target size in pixels.
	auto windowWidth = ConvertDipsToPixels(
		_Get_width_from_rect(m_windowBounds)
		);
	auto windowHeight = ConvertDipsToPixels(
		_Get_height_from_rect(m_windowBounds)
		);

	// The width and height of the swap chain must be based on the window's
	// landscape-oriented width and height. If the window is in a portrait
	// orientation, the dimensions must be reversed.

	m_orientation = deviceMode.dmOrientation;
	bool swapDimensions = m_orientation == DMORIENT_PORTRAIT;
	m_d3dRenderTargetSize.width = swapDimensions ? windowHeight : windowWidth;
	m_d3dRenderTargetSize.height = swapDimensions ? windowWidth : windowHeight;

	if (m_swapChain != nullptr)
	{
		// If the swap chain already exists, resize it.
		HRESULT hr = m_swapChain->ResizeBuffers(
			2, // Double-buffered swap chain.
			static_cast<UINT>(m_d3dRenderTargetSize.width),
			static_cast<UINT>(m_d3dRenderTargetSize.height),
			DXGI_FORMAT_B8G8R8A8_UNORM,
			0
			);

		if (hr == DXGI_ERROR_DEVICE_REMOVED)
		{
			HandleDeviceLost();
			return;
		}
		else
		{
			ThrowIfFailed(hr);
		}
	}
	else
	{
		// Otherwise, create a new one using the same adapter as the existing Direct3D device.
		DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 };
		swapChainDesc.Width = static_cast<UINT>(m_d3dRenderTargetSize.width); // Match the size of the window.
		swapChainDesc.Height = static_cast<UINT>(m_d3dRenderTargetSize.height);
		swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // This is the most common swap chain format.
		swapChainDesc.Stereo = false;
		swapChainDesc.SampleDesc.Count = 1; // Don't use multi-sampling.
		swapChainDesc.SampleDesc.Quality = 0;
		swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
		swapChainDesc.BufferCount = 2; // Use double-buffering to minimize latency.
		swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
		swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // All Windows Store apps must use this SwapEffect.
		swapChainDesc.Flags = 0;

		ComPtr<IDXGIDevice1> dxgiDevice;
		ThrowIfFailed(m_d3dDevice.As(&dxgiDevice));

		ComPtr<IDXGIAdapter> dxgiAdapter;
		ThrowIfFailed(dxgiDevice->GetAdapter(&dxgiAdapter));

		ComPtr<IDXGIFactory2> dxgiFactory;
		ThrowIfFailed(dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory)));

		DXGI_SWAP_CHAIN_FULLSCREEN_DESC fullscreenDesc;
		fullscreenDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
		fullscreenDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
		fullscreenDesc.RefreshRate.Denominator = 1;
		fullscreenDesc.RefreshRate.Numerator = 60;
		fullscreenDesc.Windowed = TRUE;

		ThrowIfFailed(
			dxgiFactory->CreateSwapChainForHwnd(
				m_d3dDevice.Get(),
				m_window,
				&swapChainDesc,
				&fullscreenDesc,
				nullptr,
				&m_swapChain
				)
			);

		// Ensure that DXGI does not queue more than one frame at a time. This both reduces latency and
		// ensures that the application will only render after each VSync, minimizing power consumption.
		ThrowIfFailed(
			dxgiDevice->SetMaximumFrameLatency(1)
			);
	}

	// Set the proper orientation for the swap chain, and generate 2D and
	// 3D matrix transformations for rendering to the rotated swap chain.
	// Note the rotation angle for the 2D and 3D transforms are different.
	// This is due to the difference in coordinate spaces.  Additionally,
	// the 3D matrix is specified explicitly to avoid rounding errors.
	DXGI_MODE_ROTATION rotation = DXGI_MODE_ROTATION_UNSPECIFIED;
	if (deviceMode.dmOrientation == DMDO_270) // 270 portrait
	{

		rotation = DXGI_MODE_ROTATION_ROTATE270;
		m_orientationTransform2D = Matrix3x2F::Rotation(270.0f) * Matrix3x2F::Translation(0.0f,
			_Get_width_from_rect(m_windowBounds)
			);
		m_orientationTransform3D = XMFLOAT4X4( // 90-degree Z-rotation
			0.0f, 1.0f, 0.0f, 0.0f,
			-1.0f, 0.0f, 0.0f, 0.0f,
			0.0f, 0.0f, 1.0f, 0.0f,
			0.0f, 0.0f, 0.0f, 1.0f
			);

	}
	else if (deviceMode.dmOrientation == DMDO_180) // 180 landscape flipped
	{
		rotation = DXGI_MODE_ROTATION_ROTATE180;
		m_orientationTransform2D = Matrix3x2F::Rotation(180.0f) * Matrix3x2F::Translation(
			_Get_width_from_rect(m_windowBounds),
			_Get_height_from_rect(m_windowBounds)
			);

		m_orientationTransform3D = XMFLOAT4X4( // 180-degree Z-rotation
			-1.0f, 0.0f, 0.0f, 0.0f,
			0.0f, -1.0f, 0.0f, 0.0f,
			0.0f, 0.0f, 1.0f, 0.0f,
			0.0f, 0.0f, 0.0f, 1.0f
			);
	}
	else if (deviceMode.dmOrientation == DMDO_90)// 90 portrait flipped
	{
		rotation = DXGI_MODE_ROTATION_ROTATE90;
		m_orientationTransform2D = Matrix3x2F::Rotation(90.0f) * Matrix3x2F::Translation(
			_Get_height_from_rect(m_windowBounds),
			0.0f
			);

		m_orientationTransform3D = XMFLOAT4X4( // 270-degree Z-rotation
			0.0f, -1.0f, 0.0f, 0.0f,
			1.0f, 0.0f, 0.0f, 0.0f,
			0.0f, 0.0f, 1.0f, 0.0f,
			0.0f, 0.0f, 0.0f, 1.0f
			);
	} 
	else if (deviceMode.dmOrientation == DMDO_DEFAULT)
	{
		rotation = DXGI_MODE_ROTATION_IDENTITY;
		m_orientationTransform2D = Matrix3x2F::Identity();
		m_orientationTransform3D = XMFLOAT4X4( // 0-degree Z-rotation
			1.0f, 0.0f, 0.0f, 0.0f,
			0.0f, 1.0f, 0.0f, 0.0f,
			0.0f, 0.0f, 1.0f, 0.0f,
			0.0f, 0.0f, 0.0f, 1.0f
			);
	}

	ThrowIfFailed(
		m_swapChain->SetRotation(rotation)
		);

	// Create a Direct3D render target view of the swap chain back buffer.
	ComPtr<ID3D11Texture2D> backBuffer;
	ThrowIfFailed(
		m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer))
		);

	ThrowIfFailed(
		m_d3dDevice->CreateRenderTargetView(
			backBuffer.Get(),
			nullptr,
			&m_d3dRenderTargetView
			)
		);

	// Create a depth stencil view for use with 3D rendering if needed.
	CD3D11_TEXTURE2D_DESC depthStencilDesc(
		DXGI_FORMAT_D24_UNORM_S8_UINT,
		static_cast<UINT>(m_d3dRenderTargetSize.width),
		static_cast<UINT>(m_d3dRenderTargetSize.height),
		1,
		1,
		D3D11_BIND_DEPTH_STENCIL
		);

	ComPtr<ID3D11Texture2D> depthStencil;
	ThrowIfFailed(
		m_d3dDevice->CreateTexture2D(
		&depthStencilDesc,
		nullptr,
		&depthStencil
		)
		);

	CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D);
	ThrowIfFailed(
		m_d3dDevice->CreateDepthStencilView(
			depthStencil.Get(),
			&depthStencilViewDesc,
			&m_d3dDepthStencilView
			)
		);

	// Set the 3D rendering viewport to target the entire window.
	m_viewport = static_cast<D3D11_VIEWPORT>(CD3D11_VIEWPORT(
		0.0f, 0.0f, // top left 
		m_d3dRenderTargetSize.width, // w 
		m_d3dRenderTargetSize.height // h
		));

	m_d3dContext->RSSetViewports(
		1, &m_viewport
		);

	// Create a Direct2D target bitmap associated with the
	// swap chain back buffer and set it as the current target.
	D2D1_BITMAP_PROPERTIES1 bitmapProperties =
		BitmapProperties1(
		D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
		PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
		m_dpi,
		m_dpi
		);

	ComPtr<IDXGISurface> dxgiBackBuffer;
	ThrowIfFailed(m_swapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiBackBuffer)));

	ThrowIfFailed(
		m_d2dContext->CreateBitmapFromDxgiSurface(
		dxgiBackBuffer.Get(),
		&bitmapProperties,
		&m_d2dTargetBitmap
		)
		);

	m_d2dContext->SetTarget(
		m_d2dTargetBitmap.Get()
		);

	// Grayscale text anti-aliasing is recommended for all Windows Store apps.
	// Desktop as well.
	m_d2dContext->SetTextAntialiasMode(
		D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE
		);
}

#endif // All implementations
